#include <linux/module.h>
#include <linux/highmem.h>
#include <asm/desc.h>
#include <asm/page.h>

#include "exceptions.h"
#include "paging.h"
#include "testing.h"
#include "paging_api.h"

#define info(...) printk("<1> info " __VA_ARGS__)

#define PFEC(x) ( ((x) & 1) + \
                  (((x)>>1) & 1)*10 + \
                  (((x)>>2) & 1)*100 + \
                  (((x)>>3) & 1)*1000 + \
                  (((x)>>4) & 1)*10000 )
                  
#pragma GCC push_options
#pragma GCC optimize ("O0")


#define virt_to_pfn(x) (virt_to_phys(x) >> PAGE_SHIFT)

void int_info_header(void)
{
  info("%-10s%6s%9s%11s%11s",
       "test",
       "int?",
       "int num",
       "err code?",
       "err_code"
      );
}

void int_info(char *name)
{
  info("%-10s%6s%9hu%11s%11d",
       name,
       (last_int_info.interrupt ? "yes" : "no"),
       last_int_info.int_num,
       (last_int_info.has_error_code ? "yes" : "no"),
       PFEC(last_int_info.unused2)
      );
}

struct paging_vertex vertices[5];
struct paging_entry entries[4];
struct paging pg;

/*
 * init simple paging
 * 
 * 0 -> 1 -> 2 -> 3 -> [4]
 */

static void my_init_paging(void)
{
  int i;
  pg.paging_mode = PM_64;
  pg.root = vertices;
  pg.vertices = vertices;
  pg.structs = vertices;
  pg.pages = &vertices[4];
  
  pg.n_vertices = 5;
  
  for(i=0; i<4; ++i) {
    vertices[i].entries = &entries[i];
    vertices[i].n_entries = 1;
    vertices[i].in_memory = 1;
    vertices[i].offset_mask = OFFSET_MASK_4KB;
    
    vertices[i].entries[0].pos = 1;
    vertices[i].entries[0].child = &vertices[i+1];
    vertices[i].entries[0].flags = PF_PRESENT | PF_WRITE | PF_UNEXECUTABLE;
  }

  vertices[4].entries = 0;
  vertices[4].offset_mask = OFFSET_MASK_4KB;
}


struct test_case cases[6];
struct test t;
char *case_comments[6];

static void my_init_test(void)
{
  vaddr_t a1 = (vaddr_t) (0llu << 39 |
                          (u64)vertices[1].entries[0].pos << 30 |
                          (u64)vertices[2].entries[0].pos << 21 |
                          (u64)vertices[3].entries[0].pos << 12 |
                          17);
  
  vaddr_t a2 = (vaddr_t) (0llu << 39 |
                          ((u64)vertices[1].entries[0].pos + 2) << 30 |
                          (u64)vertices[2].entries[0].pos << 21 |
                          (u64)vertices[3].entries[0].pos << 12 |
                          17);
  
  
  cases[0].id = 0;
  cases[0].action = ACTION_READ;
  cases[0].vaddr = a1;
  cases[0].vertex = vertices + 4;
  case_comments[0] = "read from [X][W][P]";
  
  cases[1].id = 1;
  cases[1].action = ACTION_WRITE;
  cases[1].vaddr = a1;
  cases[1].vertex = vertices + 4;
  case_comments[1] = "write to [X][W][P]";
  
  cases[2].id = 2;
  cases[2].action = ACTION_RUN;
  cases[2].vaddr = a1;
  cases[2].vertex = vertices + 4;
  case_comments[2] = "run [X][W][P]";
  
  cases[3].id = 3;
  cases[3].action = ACTION_READ;
  cases[3].vaddr = a2;
  cases[3].vertex = 0;
  case_comments[3] = "read from [!P]";
  
  cases[4].id = 4;
  cases[4].action = ACTION_WRITE;
  cases[4].vaddr = a2;
  cases[4].vertex = 0;
  case_comments[4] = "write to [!P]";
  
  cases[5].id = 5;
  cases[5].action = ACTION_RUN;
  cases[5].vaddr = a2;
  cases[5].vertex = 0;
  case_comments[5] = "run at [!P]";
  
  t.cases = cases;
  t.n_cases = 6;
  t.pg = &pg;
}


void send_to_host(const void *buf, int count)
{
  struct test_case_result *res;
  if (count != sizeof(struct test_case_result)) {
    info("send_to_host error: count = %d", count);
    return;
  }
  
  res = (struct test_case_result *) buf;
  if (res->int_info.interrupt) {
    if (res->int_info.int_num == 14) {
      printk("\n#%d: %s\n  page fault: %05d [IRUWP]",
             res->id,
             case_comments[res->id],
             PFEC(res->int_info.unused2));
    }
    else {
      printk("\n#%d: %s\n  interrupt %hu",
             res->id,
             case_comments[res->id],
             res->int_info.int_num);
    }
  }
  else {
    printk("\n#%d: %s\n no interrupts",
            res->id,
            case_comments[res->id]);
  }
}

int kk;
static int my_init(void)
{
    //u64 *p;
    
    info("begin");
    

    init_exception_handling();
    info("begin");
    my_init_paging();
   info("begin");
    init_paging(&pg);
    info("begin");
    my_init_test();
info("begin");
    init_test(&t);
    info("begin");
    run_test(&t);
    
   // info("%llu", *p);
    
    info("finish");
    
    return 0;
}

static void my_exit(void) {
    info("exit");
}

#pragma GCC pop_options

module_init(my_init);
module_exit(my_exit);
